#pragma once

#include <xy/render/renderer.h>

namespace xy
{

namespace render
{

struct primitive_material
{
    color_t color{0xffffffff};
    float reflectFactor{0.0f};
    float refractFactor{0.0f};
};

struct primitive_sphere
{
    geom::vec3f center;
    float radius;
    primitive_material material;

    std::optional<float> intersect_single_sided(const ray &r) const
    {
        geom::vec3f oc = center - r.origin;
        bool inside = oc.square_modulus() < (radius * radius);
        if (inside)
            return std::nullopt;

        float a = dot(oc, r.direction);
        if (a < 0)
            return std::nullopt;

        float dt = radius * radius - (oc - r.direction * a).square_modulus();
        if (dt < 0)
            return std::nullopt;

        return a - std::sqrt(dt);
    }

    std::optional<float> intersect_double_sided(const ray &r) const
    {
        geom::vec3f oc = center - r.origin;
        bool outside = oc.square_modulus() > (radius * radius);
        float a = dot(oc, r.direction);
        if (outside && a < 0)
            return std::nullopt;

        float dt = radius * radius - (oc - r.direction * a).square_modulus();
        if (dt < 0)
            return std::nullopt;

        return outside ? a - std::sqrt(dt) : a + std::sqrt(dt);
    }

    geom::vec3f normal(const geom::vec3f &intersection) const
    {
        return (intersection - center).normalize();
    }
};

template <typename raytracer_impl, typename primitive>
    requires(hittable<primitive>)
struct raytracer
{
    using raytracer_t = raytracer_impl;
    using primitive_t = primitive;
    using light_t = typename raytracer_t::light;
    using uniform_t = typename raytracer_t::uniform_varyings;

    // 光线投射器
    std::function<color_t(const std::vector<primitive_t> &, const light_t &, const ray &, const color_t &, int)>
        raycaster{raytracer_t::template cast_ray<primitive_t>};

    uniform_t uniform{};
    std::vector<light_t> lights{};
    std::vector<primitive_t> objects{};
};

struct whited_style_raytracer_impl
{
    struct light
    {
        geom::vec3f position;
        float diffuse;
        float specular;
    };

    struct uniform_varyings
    {
        float fovy{30.0f};
        float zNear{0.1f};
        int samplePerPixel{1};
        int maxDepth{4};
        color_t bg{0xff32b2cb};
        geom::vec3f viewPos{0.0f, 0.0f, 0.0f};
        geom::vec3f viewDir{0.0f, 0.0f, -1.0f};
    };

    template <typename T>
        requires hittable<T>
    static color_t cast_ray(const std::vector<T> &objects, const light &l, const ray &r, const color_t &bg, int depth)
    {
        if (depth < 0)
            return color_t(0, 0, 0, 255);

        // 找到光线与物体的最近交点
        std::optional<std::size_t> nearest = std::nullopt;
        float t = std::numeric_limits<float>::max();
        for (std::size_t k = 0; k < objects.size(); k++)
        {
            if (auto ti = objects[k].intersect_single_sided(r))
            {
                if (ti.value() < t)
                {
                    nearest = k;
                    t = ti.value();
                }
            }
        }

        if (nearest)
        {
            const auto &obj = objects[nearest.value()];

            // 光线与其它物体的相交检测
            auto intersection = r.at(t);
            auto normal = obj.normal(intersection);
            auto origin = intersection + normal * 0.01f;

            // 轻微偏移，防止与自身相交
            bool isShadowed = false;
            ray shadowRayTo(l.position, origin - l.position);
            ray shadowRayFrom(origin, -shadowRayTo.direction);
            for (const auto &obj : objects)
            {
                // 注意需要检查两个方向上的相交
                if (obj.intersect_single_sided(shadowRayTo) && obj.intersect_single_sided(shadowRayFrom))
                {
                    isShadowed = true;
                    break;
                }
            }

            geom::vec3f lightDir = (l.position - intersection).normalize();
            geom::vec3f halfway = (lightDir - r.direction).normalize();
            geom::vec3f reflectDir = geom::reflect(-lightDir, normal);
            geom::vec3f refractDir = geom::refract(-lightDir, normal, 1.0f / 1.33f).value();

            // 间接光照
            color_t reflect = cast_ray(objects, l, ray{intersection + normal * 0.01f, reflectDir}, bg, depth - 1) *
                              obj.material.reflectFactor;
            color_t refract = cast_ray(objects, l, ray{intersection - normal * 0.01f, refractDir}, bg, depth - 1) *
                              obj.material.refractFactor;

            // 无直接光照时，使用间接光照（无法解决的问题：反射和折射路径不是所有间接光路径，例如光线可以穿过玻璃球）
            if (isShadowed)
                return reflect + refract;

            // 直接光照
            color_t diffuse = obj.material.color * std::abs(dot(normal, lightDir)) * l.diffuse;
            color_t specular = obj.material.color * std::pow(std::abs(dot(normal, halfway)), 64.0f) * l.specular;
            return diffuse + specular + reflect + refract;
        }

        return bg;
    }
};

using whited_style_raytracer = raytracer<whited_style_raytracer_impl, primitive_sphere>;

} // namespace render

} // namespace xy
